#ifndef APP_H
#define APP_H

// Copyright (c) 2010 Omer Goshen
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

#include "MudBath.h"

#include <lua.hpp>
#include <v8.h>

#include "events/EventDispatcher.h"

using namespace std;
using namespace Ogre;

//{{{ Forward
class Object;
class EventDispatcher;

class HUD;
class Console;

class GenericCamera;
class FlyCamera;
class ChaseCamera;
class OrbitCamera;

class InputManager;
class SoundManager;

class GameState;
class EditorState;

class TransformWidget;
//}}}


struct FrameTimes {
    unsigned render;
    unsigned gui;
    unsigned script;
    unsigned physics;
	unsigned total;
};


/**
 * \class App
 * \brief The main application class
 * Runs the main loop, listens for window and input events, has getters for all
 * major components and manages the different GameStates.
 * 
 */
class App :
	//{{{ Hardcoded events
	public Ogre::FrameListener,
	public Ogre::WindowEventListener,
	public OIS::KeyListener,
	public OIS::MouseListener,
	//}}}
	
	public EventDispatcher
{
public:
	static const Ogre::String version;

	//{{{ Singleton
	static App* getSingletonPtr(void)
	{
		if ( !ms_instance )
			ms_instance = new App();
		return ms_instance;
	};

	static App& getInstance(void)
	{
		if ( !ms_instance )
			ms_instance = new App();
		return *ms_instance;
	}

	static App& getSingleton(void) { return getInstance(); }
	//}}}
	
	//{{{ State
	void 		start(GameState* state) throw(Ogre::Exception);
	bool		run();
	void		shutdown();
	inline void quit() { mQuit = true; }
	//}}}
	
	//{{{ GameStates
	void 		changeState(GameState* state);
	void		pushState(GameState* state);
	void 		popState();
	//}}}
	
	//{{{ Physics
	inline bool getPhysicsActive() 				{ return mPhysicsActive; }
	inline void setPhysicsActive(bool b)        { mPhysicsActive = b; }
	inline bool getShowDebug() 					{ return dbgdraw->getDebugMode(); }
	inline void setShowDebug(bool b) 			{ dbgdraw->setDebugMode(b); }
	inline void toggleDebug()					{ setShowDebug(!getShowDebug()); }
	inline void togglePhysics() 				{ mPhysicsActive = !mPhysicsActive; }
	//}}}
	
	//{{{ Scene
	void 		loadSceneXml(const Ogre::String& dotScene);
	//}}}
	
    //{{{ Getters
	inline InputManager*    			getInputManager() 						{ return mInputMgr; }
    inline SoundManager*   				getSoundManager() 						{ return mSoundMgr; }
	inline btDiscreteDynamicsWorld*		getDynWorld() 							{ return phyWorld; }
	inline Camera*          			getCamera() 							{ return mCamera; }
	inline RenderWindow*				getWindow() 							{ return mWindow; }
	inline SceneManager*   				getSceneManager() 						{ return mSceneMgr; }
	inline SceneNode*       			getRootSceneNode() 						{ return mSceneMgr->getRootSceneNode(); }
	inline Console*         			getConsole() 							{ return console; }
	inline Camera* 						setCamera(Camera* c) 					{ return mCamera = c; }
	inline SceneManager* 				setSceneManager(SceneManager* mgr)		{ return mSceneMgr = mgr; }
	inline btDiscreteDynamicsWorld* 	setDynWorld(btDiscreteDynamicsWorld* w) { return phyWorld = w; }
	inline BtOgre::DebugDrawer* 		getDebugDrawer() 						{ return dbgdraw; }
	inline lua_State*					getLuaState()							{ return L; }
	inline v8::Persistent<v8::Context> 	getContext() 							{ return js_ctx; }
	inline Ogre::Timer*					getTimer()								{ return mTimer; }
    inline FrameTimes&                  getFrameStats()                         { return mFrameStats; }
	inline unsigned long 				getFrameTime() 							{ return mFrameTime; }
    //}}}

	//{{{ Events
	sigc::signal<void> onFrameStarted;	
	sigc::signal<void> onStart;	
	sigc::signal<void> onShutdown;	
	//}}}
	
	//!TODO:
	GenericCamera* mCameraNode;
	
private:
	App();
	virtual ~App();
	
	//{{{ Boot\Init functions
	bool configure(void);
	void setupResources(void);
	
	void initInput();
	void initBullet();
	void initDeferredShading() throw(Ogre::Exception);
	void initLua();
	//}}}

	std::vector<GameState*> mStates;


	//{{{ Ogre\OIS hardcoded event handlers
	bool frameRenderingQueued(const Ogre::FrameEvent& evt);
	bool frameStarted(const Ogre::FrameEvent& evt);
	bool frameEnded(const Ogre::FrameEvent& evt);

	bool keyPressed( const OIS::KeyEvent &e );
	bool keyReleased( const OIS::KeyEvent &e );

	bool mouseMoved( const OIS::MouseEvent &e );
	bool mousePressed( const OIS::MouseEvent &e, OIS::MouseButtonID id );
	bool mouseReleased( const OIS::MouseEvent &e, OIS::MouseButtonID id );

	void windowResized(RenderWindow* rw);
	//}}}

	//{{{ Updating
	void stepPhysics();
	void stepScripts();
	void updateStatsOverlay();
	//}}}



protected:
	/** The default options file **/
	ConfigFile mCF;

	bool mQuit;
	
	//{{{ Ogre
	Ogre::Root* mRoot;
	Ogre::SceneManager* mSceneMgr;
	Ogre::Camera* mCamera;
	Ogre::Viewport* mViewport;
	Ogre::RenderWindow* mWindow;
	Ogre::RenderSystem* mSystem;

	Ogre::Timer* mTimer;

	Overlay* mDebugOverlay;
	//}}}
	
    //{{{ Singletons
	Console 			*console;
	InputManager       	*mInputMgr;
	SoundManager 	    *mSoundMgr;
    //}}}
	
	//{{{ Script
	lua_State *L;
	v8::Persistent<v8::Context> js_ctx;
    bool     					mScriptActive;
	//}}}
	
    //{{{ Bullet
	btDiscreteDynamicsWorld *phyWorld;
	BtOgre::DebugDrawer *dbgdraw;

	bool 	 	mPhysicsActive;
	btScalar 	mPhysicsFixedTimeStamp;
	btScalar 	mTimerMultiplier;
    //}}}

	//{{{ Time
	unsigned long 	mLastUpdateTime;
	unsigned long 	mFrameTime;

    FrameTimes 		mFrameStats;
	unsigned long 	mFrameStatsUpdateTime;
	//}}}

	// The static App instance
	static App* ms_instance;
};


#endif // __APP_H__
